<template>
  <view
    class="tabs"
    :class="[`tabs--${type}`, { 'tabs--sticky': sticky, 'tabs--animated': animated, 'tabs--scrollspy': scrollspy }]"
  >
    <view class="tabs__titles" :class="[{ unscrollable: !scrollable }]" :style="[getTitleStyle]">
      <scroll-view class="tabs__scroll" :scroll-x="scrollable" :scroll-left="scrollLeft" scroll-with-animation>
        <view class="tabs__list">
          <view
            v-for="(tab, tabIndex) in tabs"
            :key="tabIndex"
            class="tabs__title"
            :class="[{ active: tab.paneKey === activeValue, 'active--big': big && tab.paneKey === activeValue }]"
            @click="handleTabChange(tab, tabIndex)"
          >
            <view class="bg" />
            <view class="title">
              {{ tab.title }}
            </view>
          </view>
        </view>
      </scroll-view>
    </view>
    <view :style="[{ overflow: 'hidden' }, contentBoxStyle]">
      <view class="tabs__contents" :style="[getContentStyle]">
        <slot />
      </view>
    </view>
  </view>
</template>

<script lang="ts">
import { defineComponent, reactive, provide, toRefs, computed, watch } from '@vue/composition-api'
import boundingClientRect from '@/utils/bounding-client-rect'
import getCurrentComponent from '@/utils/get-current-component'
import sleep from '@/utils/sleep'
import { usePageScroll } from '@/hooks'
import addColor from '@/utils/add-color'
import addUnit from '@/utils/add-unit'

export interface ITabpane {
  title: string
  disabled?: boolean
  paneKey: string | number
}

export default defineComponent({
  props: {
    value: {
      type: [String, Number],
      default: 0,
    },
    scrollable: {
      type: Boolean,
      default: false,
    },
    background: {
      type: String,
      default: 'transparent',
    },
    type: {
      type: String,
      default: 'line', // line card smile scale trapezoid
    },
    sticky: {
      type: Boolean,
      default: false,
    },
    scrollspy: {
      type: Boolean,
      default: false,
    },
    animated: {
      type: Boolean,
      default: false,
    },
    lazyRender: {
      type: Boolean,
      default: true,
    },
    color: {
      type: String,
      default: null,
    },
    activeColor: {
      type: String,
      default: null,
    },
    bg: {
      type: String,
      default: null,
    },
    activeBg: {
      type: String,
      default: null,
    },
    barBg: {
      type: String,
      default: null,
    },
    barWidth: {
      type: String,
      default: '100%',
    },
    customContentStyle: {
      type: Object,
      default: () => ({}),
    },
    contentBoxStyle: {
      type: Object,
      default: () => ({}),
    },
    big: {
      type: Boolean,
      default: false,
    },
  },
  emits: ['input', 'click'],
  setup(props, { emit }) {
    const vm = getCurrentComponent()
    let _scrollTop
    const state = reactive({
      tabs: [] as ITabpane[],
      activeValue: props.value,
      activeIndex: 0,
      scrollLeft: 0,
      scrollLock: false,
    })
    const setTitleScroll = async (index) => {
      if (!props.scrollable) {
        return
      }

      const rects: any = await boundingClientRect('.tabs__title', vm, true)

      const rect = rects[index]

      if (rect == null) {
        return
      }

      const lineRect: any = await boundingClientRect('.tabs__list', vm)

      let lineOffsetLeft = rects.slice(0, index).reduce((prev, curr) => prev + curr.width, 0)

      lineOffsetLeft += (rect.width - lineRect.width) / 2

      state.scrollLeft = lineOffsetLeft
    }
    const setContentScroll = async (index) => {
      if (!props.scrollspy) {
        return
      }
      const rects: any = await boundingClientRect('.tabpane', vm, true)

      const scrollTop = rects[index].top - 32 + _scrollTop + 1

      uni.pageScrollTo({
        scrollTop,
        duration: 100,
      })
      state.scrollLock = true
      await sleep(200)
      state.activeValue = index
      state.scrollLock = false
    }
    const handleTabChange = async (tab, index) => {
      state.activeValue = tab.paneKey
      state.activeIndex = index
      emit('input', state.activeValue)
      emit('click', tab, index)

      tab.vm.setRender(tab.paneKey, state.activeValue)

      setTitleScroll(index)

      setContentScroll(index)
    }
    const getTitleStyle = computed(() => {
      const colorMap = {
        line: ['#666', '#262626'],
        card: ['#666', '#fff'],
        smile: ['#666', '#ff0000'],
        scale: ['#666', '#262626'],
        trapezoid: ['#A75959', '#262626'],
      }
      const [color, activeColor] = colorMap[props.type]
      const bgColorMap = {
        line: [null, null],
        card: ['#fff', 'linear-gradient(270deg,  #248DFA 0%, #248DFA 100%)'],
        smile: [null, null],
        scale: [null, null],
        trapezoid: ['#ffe5e5', '#fff'],
      }
      const [bg, activeBg] = bgColorMap[props.type]
      const barMap = {
        line: `linear-gradient(270deg,  #248DFA 0%, #248DFA 100%)`,
        scale: '#000',
      }
      const barBg = barMap[props.type]
      const obj: any = {}

      obj['--titles-bg'] = addColor(props.background)
      obj['--color'] = addColor(props.color ? props.color : color)
      obj['--active-color'] = addColor(props.activeColor ? props.activeColor : activeColor)
      obj['--bg'] = addColor(props.bg ? props.bg : bg)
      obj['--active-bg'] = addColor(props.activeBg ? props.activeBg : activeBg)
      obj['--bar-bg'] = addColor(props.barBg ? props.barBg : barBg)
      obj['--bar-width'] = addUnit(props.barWidth)

      return obj
    })
    const getContentStyle = computed(() => {
      if (props.scrollspy) {
        return props.customContentStyle
      }

      return {
        transform: `translate3d(-${state.activeIndex * 100}%, 0px, 0px)`,
        ...props.customContentStyle,
      }
    })

    usePageScroll(async ({ scrollTop }) => {
      _scrollTop = scrollTop
      if (!props.scrollspy || state.scrollLock) {
        return
      }
      const rects: any = await boundingClientRect('.tabpane', vm, true)

      for (let i = 0; i < rects.length; i++) {
        const rect = rects[i]
        const nextRect = rects[i + 1]
        const nextOut = nextRect ? nextRect.top > 32 : true

        if (rect.top < 32 && nextOut) {
          state.activeValue = i
          break
        }
      }
    })

    watch(
      () => props.value,
      (newVal) => {
        state.activeValue = newVal
      }
    )

    let setActiveIndexTimer
    const setActiveIndex = () => {
      state.activeIndex = state.tabs.findIndex((x) => x.paneKey === props.value)
    }

    provide('tabsParent', {
      tabs: state.tabs,
      lazyRender: props.scrollspy ? false : props.lazyRender,
      addTab(tab) {
        state.tabs.push(tab)

        tab.vm.setRender(tab.paneKey, state.activeValue)

        if (setActiveIndexTimer) {
          clearTimeout(setActiveIndexTimer)
        }
        // 等待tabs都加载完毕，🈯只执行一次
        setActiveIndexTimer = setTimeout(setActiveIndex, 20)
      },
      removeTab(paneKey) {
        const index = state.tabs.findIndex((x) => x.paneKey == paneKey)

        state.tabs.splice(index, 1)
      },
      setTitle(paneKey, title) {
        const tab: any = state.tabs.find((x) => x.paneKey == paneKey)

        tab.title = title
      },
    })

    return {
      ...toRefs(state),
      handleTabChange,
      getTitleStyle,
      getContentStyle,
    }
  },
})
</script>
<style lang="scss" scoped>
.tabs {
  // overflow: hidden;
  // display: flex;
  // flex-direction: column;
  &__titles {
    padding: 0 16rpx;
    user-select: none;
    background: transparent;
    border-radius: 0;
    display: flex;
    position: relative;
    &::before {
      content: '';
      position: absolute;
      z-index: 0;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: var(--titles-bg);
    }
    &.unscrollable {
      .tabs__title {
        flex: 1 0 auto;
        width: 0;
      }
    }
  }
  &__list {
    display: flex;
  }
  &__title {
    position: relative;
    font-size: 28rpx;
    padding: 0 32rpx;
    white-space: nowrap;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #1a1a1a;
  }
  &__contents {
    display: flex;
  }
  &--scrollspy {
    .tabs__contents {
      flex-direction: column;
    }
  }
  &--animated {
    .tabs__contents {
      transition-duration: 300ms;
    }
  }
  &--sticky {
    .tabs__titles {
      /* #ifndef APP-PLUS-NVUE */
      display: flex;
      position: -webkit-sticky;
      /* #endif */
      position: sticky;
      top: var(--window-top);
      z-index: 960;
    }
  }
  &--line {
    .tabs__list {
      height: 98rpx;
      // padding-top: 16rpx;
    }
    .tabs__title {
      color: var(--color);
      .title {
        position: relative;

        &::after {
          position: absolute;
          bottom: -23rpx;
          left: 50%;
          -webkit-transform: translate(-50%, 0);
          transform: translate(-50%);
          content: ' ';
          background: var(--bar-bg);
          border-radius: 2rpx;
          width: 0;
          height: 0;
          transition: width 0.3s ease;
        }
      }
      &.active {
        font-weight: bold;
        color: var(--active-color);
        .title {
          &::after {
            width: var(--bar-width);
            height: 5rpx;
          }
        }
      }
    }
  }
  &--card {
    .tabs__list {
      height: 96rpx;
      padding: 16rpx 80rpx;
    }
    .tabs__title {
      background: var(--bg);
      color: var(--color);
      // transition: color 0.3s ease;
      &:first-child {
        border-radius: 8rpx 0 0 8rpx;
      }
      &:last-child {
        border-radius: 0 8rpx 8rpx 0;
      }
      &.active {
        color: var(--active-color);
        background: var(--active-bg);
        font-weight: bold;
      }
    }
  }
  &--smile {
    .tabs__list {
      height: 88rpx;
      padding: 0 0rpx;
    }
    .title {
      position: relative;
    }
    .title::before {
      content: '';
      position: absolute;
      top: 50%;
      margin-top: -2rpx;
      margin-left: -14rpx;
      transform: translateY(-50%);
      left: 0;
      width: 0;
      height: 0;
      transition: height 0.3s ease;
      background-size: contain;
      background-repeat: no-repeat;
      background-image: url("data:image/svg+xml,%3Csvg width='19' height='31' viewBox='0 0 19 31' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3ClinearGradient x1='111.69%25' y1='0%25' x2='111.69%25' y2='92.643%25' id='a'%3E%3Cstop stop-color='%23FF8D8D' offset='0%25'/%3E%3Cstop stop-color='%23FFC9C9' stop-opacity='.135' offset='100%25'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M15 0a4 4 0 0 1 .2 7.995L15 8a7 7 0 0 0-.24 13.996L15 22a4 4 0 1 1 0 8C6.716 30 0 23.284 0 15 0 6.716 6.716 0 15 0z' fill='url(%23a)' fill-rule='nonzero'/%3E%3C/svg%3E");
    }
    .tabs__title {
      color: var(--color);
      &.active {
        font-size: 32rpx;
        color: var(--active-color);
        font-weight: bold;
        .title::before {
          width: 19rpx;
          height: 31rpx;
        }
      }
    }
  }
  &--scale {
    .tabs__list {
      height: 88rpx;
      padding: 0 0rpx;
    }
    .tabs__title {
      .title {
        position: relative;
        &::after {
          position: absolute;
          bottom: -8rpx;
          left: 50%;
          -webkit-transform: translate(-50%, 0);
          transform: translate(-50%);
          content: ' ';
          background: var(--bar-bg);
          border-radius: 2rpx;
          width: 0;
          height: 0;
          transition: width 0.3s ease;
        }
      }
    }
    .tabs__title {
      color: var(--color);
      &.active {
        font-size: 32rpx;
        color: var(--active-color);
        font-weight: bold;
        .title {
          &::after {
            width: var(--bar-width);
            height: 4rpx;
          }
        }
      }
      &.active--big {
        font-size: 36rpx;
      }
    }
  }
  &--trapezoid {
    .tabs__list {
      height: 70rpx;
      padding: 0 0rpx;
    }
    .tabs__title {
      position: relative;
      margin-top: 8rpx;
      .bg {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        border-bottom: none;
        border-radius: 16rpx 16rpx 0 0;
        background: var(--bg);
        &::before {
          content: '';
          position: absolute;
          bottom: 0;
          width: 5rpx;
          height: 5rpx;
          background: var(--active-bg);
        }
        &::after {
          content: '';
          position: absolute;
          bottom: 0;
          width: 10rpx;
          height: 10rpx;
          background-color: var(--bg);
          border-radius: 8rpx;
        }
      }

      &.active {
        .bg {
          top: -8rpx;
          z-index: 1;
          background: var(--active-bg);
        }
        &:first-child .bg {
          transform: scale(1.2, 1.12) perspective(0.8em) rotateX(3deg);
          transform-origin: bottom left;
          &::before {
            right: -5rpx;
          }
          &::after {
            right: -10rpx;
          }
        }
        &:last-child .bg {
          transform: scale(1.2, 1.12) perspective(0.8em) rotateX(3deg);
          transform-origin: bottom right;
          &::before {
            left: -5rpx;
          }
          &::after {
            left: -10rpx;
          }
        }
      }
    }
    .tabs__title {
      .title {
        position: relative;
        z-index: 2;
      }
      color: var(--color);
      transition: font-size 0.3s ease;
      &.active {
        font-size: 32rpx;
        color: var(--active-color);
        margin-right: 0;
        font-weight: bold;
      }
    }
  }
}
</style>
